/*
* jquery.layout 1.2.0
*
* Copyright (c) 2008 
*   Fabrizio Balliano (http://www.fabrizioballiano.net)
*   Kevin Dalman (http://allpro.net)
*
* Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html)
* and MIT (http://www.opensource.org/licenses/mit-license.php) licenses.
*
* $Date: 2008-12-27 02:17:22 +0100 (sab, 27 dic 2008) $
* $Rev: 203 $
* 
* NOTE: For best code readability, view this with a fixed-space font and tabs equal to 4-chars
*/
(function($) {

	$.fn.layout = function(opts) {

		/*
		* ###########################
		*   WIDGET CONFIG & OPTIONS
		* ###########################
		*/

		// DEFAULTS for options
		var 
		prefix = "ui-layout-" // prefix for ALL selectors and classNames
	, defaults = { //	misc default values
		paneClass: prefix + "pane"		// ui-layout-pane
		, resizerClass: prefix + "resizer"	// ui-layout-resizer
		, togglerClass: prefix + "toggler"	// ui-layout-toggler
		, togglerInnerClass: prefix + ""			// ui-layout-open / ui-layout-closed
		, buttonClass: prefix + "button"		// ui-layout-button
		, contentSelector: "." + prefix + "content"// ui-layout-content
		, contentIgnoreSelector: "." + prefix + "ignore"	// ui-layout-mask 
	}
	;

		// DEFAULT PANEL OPTIONS - CHANGE IF DESIRED
		var options = {
			name: ""			// FUTURE REFERENCE - not used right now
	, scrollToBookmarkOnLoad: true		// after creating a layout, scroll to bookmark in URL (.../page.htm#myBookmark)
	, defaults: { // default options for 'all panes' - will be overridden by 'per-pane settings'
		applyDefaultStyles: false		// apply basic styles directly to resizers & buttons? If not, then stylesheet must handle it
		, closable: true		// pane can open & close
		, resizable: true		// when open, pane can be resized 
		, slidable: true		// when closed, pane can 'slide' open over other panes - closes on mouse-out
		//,	paneSelector:			[ ]			// MUST be pane-specific!
		, contentSelector: defaults.contentSelector	// INNER div/element to auto-size so only it scrolls, not the entire pane!
		, contentIgnoreSelector: defaults.contentIgnoreSelector	// elem(s) to 'ignore' when measuring 'content'
		, paneClass: defaults.paneClass		// border-Pane - default: 'ui-layout-pane'
		, resizerClass: defaults.resizerClass	// Resizer Bar		- default: 'ui-layout-resizer'
		, togglerClass: defaults.togglerClass	// Toggler Button	- default: 'ui-layout-toggler'
		, buttonClass: defaults.buttonClass	// CUSTOM Buttons	- default: 'ui-layout-button-toggle/-open/-close/-pin'
		, resizerDragOpacity: 1			// option for ui.draggable
		//,	resizerCursor:			""			// MUST be pane-specific - cursor when over resizer-bar
		, maskIframesOnResize: true		// true = all iframes OR = iframe-selector(s) - adds masking-div during resizing/dragging
		//,	size:					100			// inital size of pane - defaults are set 'per pane'
		, minSize: 0			// when manually resizing a pane
		, maxSize: 0			// ditto, 0 = no limit
		, spacing_open: 6			// space between pane and adjacent panes - when pane is 'open'
		, spacing_closed: 6			// ditto - when pane is 'closed'
		, togglerLength_open: 50			// Length = WIDTH of toggler button on north/south edges - HEIGHT on east/west edges
		, togglerLength_closed: 50			// 100% OR -1 means 'full height/width of resizer bar' - 0 means 'hidden'
		, togglerAlign_open: "center"	// top/left, bottom/right, center, OR...
		, togglerAlign_closed: "center"	// 1 => nn = offset from top/left, -1 => -nn == offset from bottom/right
		, togglerTip_open: "Close"		// Toggler tool-tip (title)
		, togglerTip_closed: "Open"		// ditto
		, resizerTip: "Resize"	// Resizer tool-tip (title)
		, sliderTip: "Slide Open" // resizer-bar triggers 'sliding' when pane is closed
		, sliderCursor: "pointer"	// cursor when resizer-bar will trigger 'sliding'
		, slideTrigger_open: "click"		// click, dblclick, mouseover
		, slideTrigger_close: "mouseout"	// click, mouseout
		, hideTogglerOnSlide: false		// when pane is slid-open, should the toggler show?
		, togglerContent_open: ""			// text or HTML to put INSIDE the toggler
		, togglerContent_closed: ""			// ditto
		, showOverflowOnHover: false		// will bind allowOverflow() utility to pane.onMouseOver
		, enableCursorHotkey: true		// enabled 'cursor' hotkeys
		//,	customHotkey:			""			// MUST be pane-specific - EITHER a charCode OR a character
		, customHotkeyModifier: "SHIFT"		// either 'SHIFT', 'CTRL' or 'CTRL+SHIFT' - NOT 'ALT'
		//	NOTE: fxSss_open & fxSss_close options (eg: fxName_open) are auto-generated if not passed
		, fxName: "slide" 	// ('none' or blank), slide, drop, scale
		, fxSpeed: null		// slow, normal, fast, 200, nnn - if passed, will OVERRIDE fxSettings.duration
		, fxSettings: {}			// can be passed, eg: { easing: "easeOutBounce", duration: 1500 }
		, initClosed: false		// true = init pane as 'closed'
		, initHidden: false 		// true = init pane as 'hidden' - no resizer or spacing

		/*	callback options do not have to be set - listed here for reference only
		,	onshow_start:			""			// CALLBACK when pane STARTS to Show	- BEFORE onopen/onhide_start
		,	onshow_end:				""			// CALLBACK when pane ENDS being Shown	- AFTER  onopen/onhide_end
		,	onhide_start:			""			// CALLBACK when pane STARTS to Close	- BEFORE onclose_start
		,	onhide_end:				""			// CALLBACK when pane ENDS being Closed	- AFTER  onclose_end
		,	onopen_start:			""			// CALLBACK when pane STARTS to Open
		,	onopen_end:				""			// CALLBACK when pane ENDS being Opened
		,	onclose_start:			""			// CALLBACK when pane STARTS to Close
		,	onclose_end:			""			// CALLBACK when pane ENDS being Closed
		,	onresize_start:			""			// CALLBACK when pane STARTS to be ***MANUALLY*** Resized
		,	onresize_end:			""			// CALLBACK when pane ENDS being Resized ***FOR ANY REASON***
		*/
	}
	, north: {
		paneSelector: "." + prefix + "north" // default = .ui-layout-north
		, size: "auto"
		, resizerCursor: "n-resize"
	}
	, south: {
		paneSelector: "." + prefix + "south" // default = .ui-layout-south
		, size: "auto"
		, resizerCursor: "s-resize"
	}
	, east: {
		paneSelector: "." + prefix + "east" // default = .ui-layout-east
		, size: 200
		, resizerCursor: "e-resize"
	}
	, west: {
		paneSelector: "." + prefix + "west" // default = .ui-layout-west
		, size: 200
		, resizerCursor: "w-resize"
	}
	, center: {
		paneSelector: "." + prefix + "center" // default = .ui-layout-center
	}

		};


		var effects = { // LIST *PREDEFINED EFFECTS* HERE, even if effect has no settings
			slide: {
				all: { duration: "fast"} // eg: duration: 1000, easing: "easeOutBounce"
		, north: { direction: "up" }
		, south: { direction: "down" }
		, east: { direction: "right" }
		, west: { direction: "left" }
			}
	, drop: {
		all: { duration: "slow"} // eg: duration: 1000, easing: "easeOutQuint"
		, north: { direction: "up" }
		, south: { direction: "down" }
		, east: { direction: "right" }
		, west: { direction: "left" }
	}
	, scale: {
		all: { duration: "fast" }
	}
		};


		// STATIC, INTERNAL CONFIG - DO NOT CHANGE THIS!
		var config = {
			allPanes: "north,south,east,west,center"
	, borderPanes: "north,south,east,west"
	, zIndex: { // set z-index values here
		resizer_normal: 1		// normal z-index for resizer-bars
		, pane_normal: 2		// normal z-index for panes
		, mask: 4		// overlay div used to mask pane(s) during resizing
		, sliding: 100		// applied to both the pane and its resizer when a pane is 'slid open'
		, resizing: 10000	// applied to the CLONED resizer-bar when being 'dragged'
		, animation: 10000	// applied to the pane when being animated - not applied to the resizer
	}
	, resizers: {
		cssReq: {
			position: "absolute"
			, padding: 0
			, margin: 0
			, fontSize: "1px"
			, textAlign: "left" // to counter-act "center" alignment!
			, overflow: "hidden" // keep toggler button from overflowing
			, zIndex: 1
		}
		, cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true
			background: "#DDD"
			, border: "none"
		}
	}
	, togglers: {
		cssReq: {
			position: "absolute"
			, display: "block"
			, padding: 0
			, margin: 0
			, overflow: "hidden"
			, textAlign: "center"
			, fontSize: "1px"
			, cursor: "pointer"
			, zIndex: 1
		}
		, cssDef: { // DEFAULT CSS - applied if: options.PANE.applyDefaultStyles=true
			background: "#AAA"
		}
	}
	, content: {
		cssReq: {
			overflow: "auto"
		}
		, cssDef: {}
	}
	, defaults: { // defaults for ALL panes - overridden by 'per-pane settings' below
		cssReq: {
			position: "absolute"
			, margin: 0
			, zIndex: 2
		}
		, cssDef: {
			padding: "10px"
			, background: "#FFF"
			, border: "1px solid #BBB"
			, overflow: "auto"
		}
	}
	, north: {
		edge: "top"
		, sizeType: "height"
		, dir: "horz"
		, cssReq: {
			top: 0
			, bottom: "auto"
			, left: 0
			, right: 0
			, width: "auto"
			//	height: 	DYNAMIC
		}
	}
	, south: {
		edge: "bottom"
		, sizeType: "height"
		, dir: "horz"
		, cssReq: {
			top: "auto"
			, bottom: 0
			, left: 0
			, right: 0
			, width: "auto"
			//	height: 	DYNAMIC
		}
	}
	, east: {
		edge: "right"
		, sizeType: "width"
		, dir: "vert"
		, cssReq: {
			left: "auto"
			, right: 0
			, top: "auto" // DYNAMIC
			, bottom: "auto" // DYNAMIC
			, height: "auto"
			//	width: 		DYNAMIC
		}
	}
	, west: {
		edge: "left"
		, sizeType: "width"
		, dir: "vert"
		, cssReq: {
			left: 0
			, right: "auto"
			, top: "auto" // DYNAMIC
			, bottom: "auto" // DYNAMIC
			, height: "auto"
			//	width: 		DYNAMIC
		}
	}
	, center: {
		dir: "center"
		, cssReq: {
			left: "auto" // DYNAMIC
			, right: "auto" // DYNAMIC
			, top: "auto" // DYNAMIC
			, bottom: "auto" // DYNAMIC
			, height: "auto"
			, width: "auto"
		}
	}
		};


		// DYNAMIC DATA
		var state = {
			// generate random 'ID#' to identify layout - used to create global namespace for timers
			id: Math.floor(Math.random() * 10000)
	, container: {}
	, north: {}
	, south: {}
	, east: {}
	, west: {}
	, center: {}
		};


		var 
		altEdge = {
			top: "bottom"
		, bottom: "top"
		, left: "right"
		, right: "left"
		}
	, altSide = {
		north: "south"
		, south: "north"
		, east: "west"
		, west: "east"
	}
	;


		/*
		* ###########################
		*  INTERNAL HELPER FUNCTIONS
		* ###########################
		*/

		/**
		* isStr
		*
		* Returns true if passed param is EITHER a simple string OR a 'string object' - otherwise returns false
		*/
		var isStr = function(o) {
			if (typeof o == "string")
				return true;
			else if (typeof o == "object") {
				try {
					var match = o.constructor.toString().match(/string/i);
					return (match !== null);
				} catch (e) { }
			}
			return false;
		};

		/**
		* str
		*
		* Returns a simple string if the passed param is EITHER a simple string OR a 'string object',
		*  else returns the original object
		*/
		var str = function(o) {
			if (typeof o == "string" || isStr(o)) return $.trim(o); // trim converts 'String object' to a simple string
			else return o;
		};

		/**
		* min / max
		*
		* Alias for Math.min/.max to simplify coding
		*/
		var min = function(x, y) { return Math.min(x, y); };
		var max = function(x, y) { return Math.max(x, y); };

		/**
		* transformData
		*
		* Processes the options passed in and transforms them into the format used by layout()
		* Missing keys are added, and converts the data if passed in 'flat-format' (no sub-keys)
		* In flat-format, pane-specific-settings are prefixed like: north__optName  (2-underscores)
		* To update effects, options MUST use nested-keys format, with an effects key
		*
		* @callers  initOptions()
		* @params  JSON  d  Data/options passed by user - may be a single level or nested levels
		* @returns JSON  Creates a data struture that perfectly matches 'options', ready to be imported
		*/
		var transformData = function(d) {
			var json = { defaults: { fxSettings: {} }, north: { fxSettings: {} }, south: { fxSettings: {} }, east: { fxSettings: {} }, west: { fxSettings: {} }, center: { fxSettings: {}} };
			d = d || {};
			if (d.effects || d.defaults || d.north || d.south || d.west || d.east || d.center)
				json = $.extend(json, d); // already in json format - add to base keys
			else
			// convert 'flat' to 'nest-keys' format - also handles 'empty' user-options
				$.each(d, function(key, val) {
					a = key.split("__");
					json[a[1] ? a[0] : "defaults"][a[1] ? a[1] : a[0]] = val;
				});
			return json;
		};

		/**
		* setFlowCallback
		*
		* Set an INTERNAL callback to avoid simultaneous animation
		* Runs only if needed and only if all callbacks are not 'already set'!
		*
		* @param String   action  Either 'open' or 'close'
		* @pane  String   pane    A valid border-pane name, eg 'west'
		* @pane  Boolean  param   Extra param for callback (optional)
		*/
		var setFlowCallback = function(action, pane, param) {
			var 
			cb = action + "," + pane + "," + (param ? 1 : 0)
		, cP, cbPane
		;
			$.each(c.borderPanes.split(","), function(i, p) {
				if (c[p].isMoving) {
					bindCallback(p); // TRY to bind a callback
					return false; // BREAK
				}
			});

			function bindCallback(p, test) {
				cP = c[p];
				if (!cP.doCallback) {
					cP.doCallback = true;
					cP.callback = cb;
				}
				else { // try to 'chain' this callback
					cpPane = cP.callback.split(",")[1]; // 2nd param is 'pane'
					if (cpPane != p && cpPane != pane) // callback target NOT 'itself' and NOT 'this pane'
						bindCallback(cpPane, true); // RECURSE
				}
			}
		};

		/**
		* execFlowCallback
		*
		* RUN the INTERNAL callback for this pane - if one exists
		*
		* @param String   action  Either 'open' or 'close'
		* @pane  String   pane    A valid border-pane name, eg 'west'
		* @pane  Boolean  param   Extra param for callback (optional)
		*/
		var execFlowCallback = function(pane) {
			var cP = c[pane];

			// RESET flow-control flaGs
			c.isLayoutBusy = false;
			delete cP.isMoving;
			if (!cP.doCallback || !cP.callback) return;

			cP.doCallback = false; // RESET logic flag

			// EXECUTE the callback
			var 
			cb = cP.callback.split(",")
		, param = (cb[2] > 0 ? true : false)
		;
			if (cb[0] == "open")
				open(cb[1], param);
			else if (cb[0] == "close")
				close(cb[1], param);

			if (!cP.doCallback) cP.callback = null; // RESET - unless callback above enabled it again!
		};

		/**
		* execUserCallback
		*
		* Executes a Callback function after a trigger event, like resize, open or close
		*
		* @param String  pane   This is passed only so we can pass the 'pane object' to the callback
		* @param String  v_fn  Accepts a function name, OR a comma-delimited array: [0]=function name, [1]=argument
		*/
		var execUserCallback = function(pane, v_fn) {
			if (!v_fn) return;
			var fn;
			try {
				if (typeof v_fn == "function")
					fn = v_fn;
				else if (typeof v_fn != "string")
					return;
				else if (v_fn.indexOf(",") > 0) {
					// function name cannot contain a comma, so must be a function name AND a 'name' parameter
					var 
					args = v_fn.split(",")
				, fn = eval(args[0])
				;
					if (typeof fn == "function" && args.length > 1)
						return fn(args[1]); // pass the argument parsed from 'list'
				}
				else // just the name of an external function?
					fn = eval(v_fn);

				if (typeof fn == "function")
				// pass data: pane-name, pane-element, pane-state, pane-options, and layout-name
					return fn(pane, $Ps[pane], $.extend({}, state[pane]), $.extend({}, options[pane]), options.name);
			}
			catch (ex) { }
		};

		/**
		* cssNum
		*
		* Returns the 'current CSS value' for an element - returns 0 if property does not exist
		*
		* @callers  Called by many methods
		* @param jQuery  $Elem  Must pass a jQuery object - first element is processed
		* @param String  property  The name of the CSS property, eg: top, width, etc.
		* @returns Variant  Usually is used to get an integer value for position (top, left) or size (height, width)
		*/
		var cssNum = function($E, prop) {
			var 
			val = 0
		, hidden = false
		, visibility = ""
		;
			if (!$.browser.msie) { // IE CAN read dimensions of 'hidden' elements - FF CANNOT
				if ($.curCSS($E[0], "display", true) == "none") {
					hidden = true;
					visibility = $.curCSS($E[0], "visibility", true); // SAVE current setting
					$E.css({ display: "block", visibility: "hidden" }); // show element 'invisibly' so we can measure it
				}
			}

			val = parseInt($.curCSS($E[0], prop, true), 10) || 0;

			if (hidden) { // WAS hidden, so put back the way it was
				$E.css({ display: "none" });
				if (visibility && visibility != "hidden")
					$E.css({ visibility: visibility }); // reset 'visibility'
			}

			return val;
		};

		/**
		* cssW / cssH / cssSize
		*
		* Contains logic to check boxModel & browser, and return the correct width/height for the current browser/doctype
		*
		* @callers  initPanes(), sizeMidPanes(), initHandles(), sizeHandles()
		* @param Variant  elem  Can accept a 'pane' (east, west, etc) OR a DOM object OR a jQuery object
		* @param Integer  outerWidth/outerHeight  (optional) Can pass a width, allowing calculations BEFORE element is resized
		* @returns Integer  Returns the innerHeight of the elem by subtracting padding and borders
		*
		* @TODO  May need to add additional logic to handle more browser/doctype variations?
		*/
		var cssW = function(e, outerWidth) {
			var $E;
			if (isStr(e)) {
				e = str(e);
				$E = $Ps[e];
			}
			else
				$E = $(e);

			// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
			if (outerWidth <= 0)
				return 0;
			else if (!(outerWidth > 0))
				outerWidth = isStr(e) ? getPaneSize(e) : $E.outerWidth();

			if (!$.boxModel)
				return outerWidth;

			else // strip border and padding size from outerWidth to get CSS Width
				return outerWidth
				- cssNum($E, "paddingLeft")
				- cssNum($E, "paddingRight")
				- ($.curCSS($E[0], "borderLeftStyle", true) == "none" ? 0 : cssNum($E, "borderLeftWidth"))
				- ($.curCSS($E[0], "borderRightStyle", true) == "none" ? 0 : cssNum($E, "borderRightWidth"))
			;
		};
		var cssH = function(e, outerHeight) {
			var $E;
			if (isStr(e)) {
				e = str(e);
				$E = $Ps[e];
			}
			else
				$E = $(e);

			// a 'calculated' outerHeight can be passed so borders and/or padding are removed if needed
			if (outerHeight <= 0)
				return 0;
			else if (!(outerHeight > 0))
				outerHeight = (isStr(e)) ? getPaneSize(e) : $E.outerHeight();

			if (!$.boxModel)
				return outerHeight;

			else // strip border and padding size from outerHeight to get CSS Height
				return outerHeight
				- cssNum($E, "paddingTop")
				- cssNum($E, "paddingBottom")
				- ($.curCSS($E[0], "borderTopStyle", true) == "none" ? 0 : cssNum($E, "borderTopWidth"))
				- ($.curCSS($E[0], "borderBottomStyle", true) == "none" ? 0 : cssNum($E, "borderBottomWidth"))
			;
		};
		var cssSize = function(pane, outerSize) {
			if (c[pane].dir == "horz") // pane = north or south
				return cssH(pane, outerSize);
			else // pane = east or west
				return cssW(pane, outerSize);
		};

		/**
		* getPaneSize
		*
		* Calculates the current 'size' (width or height) of a border-pane - optionally with 'pane spacing' added
		*
		* @returns Integer  Returns EITHER Width for east/west panes OR Height for north/south panes - adjusted for boxModel & browser
		*/
		var getPaneSize = function(pane, inclSpace) {
			var 
			$P = $Ps[pane]
		, o = options[pane]
		, s = state[pane]
		, oSp = (inclSpace ? o.spacing_open : 0)
		, cSp = (inclSpace ? o.spacing_closed : 0)
		;
			if (!$P || s.isHidden)
				return 0;
			else if (s.isClosed || (s.isSliding && inclSpace))
				return cSp;
			else if (c[pane].dir == "horz")
				return $P.outerHeight() + oSp;
			else // dir == "vert"
				return $P.outerWidth() + oSp;
		};

		var setPaneMinMaxSizes = function(pane) {
			var 
			d = cDims
		, edge = c[pane].edge
		, dir = c[pane].dir
		, o = options[pane]
		, s = state[pane]
		, $P = $Ps[pane]
		, $altPane = $Ps[altSide[pane]]
		, paneSpacing = o.spacing_open
		, altPaneSpacing = options[altSide[pane]].spacing_open
		, altPaneSize = (!$altPane ? 0 : (dir == "horz" ? $altPane.outerHeight() : $altPane.outerWidth()))
		, containerSize = (dir == "horz" ? d.innerHeight : d.innerWidth)
			//	limitSize prevents this pane from 'overlapping' opposite pane - even if opposite pane is currently closed
		, limitSize = containerSize - paneSpacing - altPaneSize - altPaneSpacing
		, minSize = s.minSize || 0
		, maxSize = Math.min(s.maxSize || 9999, limitSize)
		, minPos, maxPos	// used to set resizing limits
		;
			switch (pane) {
				case "north": minPos = d.offsetTop + minSize;
					maxPos = d.offsetTop + maxSize;
					break;
				case "west": minPos = d.offsetLeft + minSize;
					maxPos = d.offsetLeft + maxSize;
					break;
				case "south": minPos = d.offsetTop + d.innerHeight - maxSize;
					maxPos = d.offsetTop + d.innerHeight - minSize;
					break;
				case "east": minPos = d.offsetLeft + d.innerWidth - maxSize;
					maxPos = d.offsetLeft + d.innerWidth - minSize;
					break;
			}
			// save data to pane-state
			$.extend(s, { minSize: minSize, maxSize: maxSize, minPosition: minPos, maxPosition: maxPos });
		};

		/**
		* getPaneDims
		*
		* Returns data for setting the size/position of center pane. Date is also used to set Height for east/west panes
		*
		* @returns JSON  Returns a hash of all dimensions: top, bottom, left, right, (outer) width and (outer) height
		*/
		var getPaneDims = function() {
			var d = {
				top: getPaneSize("north", true) // true = include 'spacing' value for p
		, bottom: getPaneSize("south", true)
		, left: getPaneSize("west", true)
		, right: getPaneSize("east", true)
		, width: 0
		, height: 0
			};

			with (d) {
				width = cDims.innerWidth - left - right;
				height = cDims.innerHeight - bottom - top;
				// now add the 'container border/padding' to get final positions - relative to the container
				top += cDims.top;
				bottom += cDims.bottom;
				left += cDims.left;
				right += cDims.right;
			}

			return d;
		};


		/**
		* getElemDims
		*
		* Returns data for setting size of an element (container or a pane).
		*
		* @callers  create(), onWindowResize() for container, plus others for pane
		* @returns JSON  Returns a hash of all dimensions: top, bottom, left, right, outerWidth, innerHeight, etc
		*/
		var getElemDims = function($E) {
			var 
			d = {} // dimensions hash
		, e, b, p // edge, border, padding
		;

			$.each("Left,Right,Top,Bottom".split(","), function() {
				e = str(this);
				b = d["border" + e] = cssNum($E, "border" + e + "Width");
				p = d["padding" + e] = cssNum($E, "padding" + e);
				d["offset" + e] = b + p; // total offset of content from outer edge
				// if BOX MODEL, then 'position' = PADDING (ignore borderWidth)
				if ($E == $Container)
					d[e.toLowerCase()] = ($.boxModel ? p : 0);
			});

			d.innerWidth = d.outerWidth = $E.outerWidth();
			d.innerHeight = d.outerHeight = $E.outerHeight();
			if ($.boxModel) {
				d.innerWidth -= (d.offsetLeft + d.offsetRight);
				d.innerHeight -= (d.offsetTop + d.offsetBottom);
			}

			return d;
		};


		var setTimer = function(pane, action, fn, ms) {
			var 
			Layout = window.layout = window.layout || {}
		, Timers = Layout.timers = Layout.timers || {}
		, name = "layout_" + state.id + "_" + pane + "_" + action // UNIQUE NAME for every layout-pane-action
		;
			if (Timers[name]) return; // timer already set!
			else Timers[name] = setTimeout(fn, ms);
		};

		var clearTimer = function(pane, action) {
			var 
			Layout = window.layout = window.layout || {}
		, Timers = Layout.timers = Layout.timers || {}
		, name = "layout_" + state.id + "_" + pane + "_" + action // UNIQUE NAME for every layout-pane-action
		;
			if (Timers[name]) {
				clearTimeout(Timers[name]);
				delete Timers[name];
				return true;
			}
			else
				return false;
		};


		/*
		* ###########################
		*   INITIALIZATION METHODS
		* ###########################
		*/

		/**
		* create
		*
		* Initialize the layout - called automatically whenever an instance of layout is created
		*
		* @callers  NEVER explicity called
		* @returns  An object pointer to the instance created
		*/
		var create = function() {
			// initialize config/options
			initOptions();

			// initialize all objects
			initContainer(); // set CSS as needed and init state.container dimensions
			initPanes(); 	// size & position all panes
			initHandles(); 	// create and position all resize bars & togglers buttons
			initResizable(); // activate resizing on all panes where resizable=true
			sizeContent("all"); // AFTER panes & handles have been initialized, size 'content' divs

			if (options.scrollToBookmarkOnLoad)
				with (self.location) if (hash) replace(hash); // scrollTo Bookmark

			// bind hotkey function - keyDown - if required
			initHotkeys();

			// bind resizeAll() for 'this layout instance' to window.resize event
			$(window).resize(function() {
				var timerID = "timerLayout_" + state.id;
				if (window[timerID]) clearTimeout(window[timerID]);
				window[timerID] = null;
				if (true || $.browser.msie) // use a delay for IE because the resize event fires repeatly
					window[timerID] = setTimeout(resizeAll, 100);
				else // most other browsers have a built-in delay before firing the resize event
					resizeAll(); // resize all layout elements NOW!
			});
		};

		/**
		* initContainer
		*
		* Validate and initialize container CSS and events
		*
		* @callers  create()
		*/
		var initContainer = function() {
			try { // format html/body if this is a full page layout
				if ($Container[0].tagName == "BODY") {
					$("html").css({
						height: "100%"
				, overflow: "hidden"
					});
					$("body").css({
						position: "relative"
				, height: "100%"
				, overflow: "hidden"
				, margin: 0
				, padding: 0		// TODO: test whether body-padding could be handled?
				, border: "none"	// a body-border creates problems because it cannot be measured!
					});
				}
				else { // set required CSS - overflow and position
					var 
					CSS = { overflow: "hidden"} // make sure container will not 'scroll'
				, p = $Container.css("position")
				, h = $Container.css("height")
				;
					// if this is a NESTED layout, then outer-pane ALREADY has position and height
					if (!$Container.hasClass("ui-layout-pane")) {
						if (!p || "fixed,absolute,relative".indexOf(p) < 0)
							CSS.position = "relative"; // container MUST have a 'position'
						if (!h || h == "auto")
							CSS.height = "100%"; // container MUST have a 'height'
					}
					$Container.css(CSS);
				}
			} catch (ex) { }

			// get layout-container dimensions (updated when necessary)
			cDims = state.container = getElemDims($Container); // update data-pointer too
		};

		/**
		* initHotkeys
		*
		* Bind layout hotkeys - if options enabled
		*
		* @callers  create()
		*/
		var initHotkeys = function() {
			// bind keyDown to capture hotkeys, if option enabled for ANY pane
			$.each(c.borderPanes.split(","), function(i, pane) {
				var o = options[pane];
				if (o.enableCursorHotkey || o.customHotkey) {
					$(document).keydown(keyDown); // only need to bind this ONCE
					return false; // BREAK - binding was done
				}
			});
		};

		/**
		* initOptions
		*
		* Build final CONFIG and OPTIONS data
		*
		* @callers  create()
		*/
		var initOptions = function() {
			// simplify logic by making sure passed 'opts' var has basic keys
			opts = transformData(opts);

			// update default effects, if case user passed key
			if (opts.effects) {
				$.extend(effects, opts.effects);
				delete opts.effects;
			}

			// see if any 'global options' were specified
			$.each("name,scrollToBookmarkOnLoad".split(","), function(idx, key) {
				if (opts[key] !== undefined)
					options[key] = opts[key];
				else if (opts.defaults[key] !== undefined) {
					options[key] = opts.defaults[key];
					delete opts.defaults[key];
				}
			});

			// remove any 'defaults' that MUST be set 'per-pane'
			$.each("paneSelector,resizerCursor,customHotkey".split(","),
			function(idx, key) { delete opts.defaults[key]; } // is OK if key does not exist
		);

			// now update options.defaults
			$.extend(options.defaults, opts.defaults);
			// make sure required sub-keys exist
			//if (typeof options.defaults.fxSettings != "object") options.defaults.fxSettings = {};

			// merge all config & options for the 'center' pane
			c.center = $.extend(true, {}, c.defaults, c.center);
			$.extend(options.center, opts.center);
			// Most 'default options' do not apply to 'center', so add only those that DO
			var o_Center = $.extend(true, {}, options.defaults, opts.defaults, options.center); // TEMP data
			$.each("paneClass,contentSelector,contentIgnoreSelector,applyDefaultStyles,showOverflowOnHover".split(","),
			function(idx, key) { options.center[key] = o_Center[key]; }
		);

			var defs = options.defaults;

			// create a COMPLETE set of options for EACH border-pane
			$.each(c.borderPanes.split(","), function(i, pane) {
				// apply 'pane-defaults' to CONFIG.PANE
				c[pane] = $.extend(true, {}, c.defaults, c[pane]);
				// apply 'pane-defaults' +  user-options to OPTIONS.PANE
				o = options[pane] = $.extend(true, {}, options.defaults, options[pane], opts.defaults, opts[pane]);

				// make sure we have base-classes
				if (!o.paneClass) o.paneClass = defaults.paneClass;
				if (!o.resizerClass) o.resizerClass = defaults.resizerClass;
				if (!o.togglerClass) o.togglerClass = defaults.togglerClass;

				// create FINAL fx options for each pane, ie: options.PANE.fxName/fxSpeed/fxSettings[_open|_close]
				$.each(["_open", "_close", ""], function(i, n) {
					var 
					sName = "fxName" + n
				, sSpeed = "fxSpeed" + n
				, sSettings = "fxSettings" + n
				;
					// recalculate fxName according to specificity rules
					o[sName] =
					opts[pane][sName]		// opts.west.fxName_open
				|| opts[pane].fxName		// opts.west.fxName
				|| opts.defaults[sName]	// opts.defaults.fxName_open
				|| opts.defaults.fxName	// opts.defaults.fxName
				|| o[sName]				// options.west.fxName_open
				|| o.fxName				// options.west.fxName
				|| defs[sName]				// options.defaults.fxName_open
				|| defs.fxName				// options.defaults.fxName
				|| "none"
				;
					// validate fxName to be sure is a valid effect
					var fxName = o[sName];
					if (fxName == "none" || !$.effects || !$.effects[fxName] || (!effects[fxName] && !o[sSettings] && !o.fxSettings))
						fxName = o[sName] = "none"; // effect not loaded, OR undefined FX AND fxSettings not passed
					// set vars for effects subkeys to simplify logic
					var 
					fx = effects[fxName] || {} // effects.slide
				, fx_all = fx.all || {} // effects.slide.all
				, fx_pane = fx[pane] || {} // effects.slide.west
				;
					// RECREATE the fxSettings[_open|_close] keys using specificity rules
					o[sSettings] = $.extend(
					{}
				, fx_all						// effects.slide.all
				, fx_pane						// effects.slide.west
				, defs.fxSettings || {}		// options.defaults.fxSettings
				, defs[sSettings] || {}		// options.defaults.fxSettings_open
				, o.fxSettings				// options.west.fxSettings
				, o[sSettings]				// options.west.fxSettings_open
				, opts.defaults.fxSettings	// opts.defaults.fxSettings
				, opts.defaults[sSettings] || {} // opts.defaults.fxSettings_open
				, opts[pane].fxSettings		// opts.west.fxSettings
				, opts[pane][sSettings] || {}	// opts.west.fxSettings_open
				);
					// recalculate fxSpeed according to specificity rules
					o[sSpeed] =
					opts[pane][sSpeed]		// opts.west.fxSpeed_open
				|| opts[pane].fxSpeed		// opts.west.fxSpeed (pane-default)
				|| opts.defaults[sSpeed]	// opts.defaults.fxSpeed_open
				|| opts.defaults.fxSpeed	// opts.defaults.fxSpeed
				|| o[sSpeed]				// options.west.fxSpeed_open
				|| o[sSettings].duration	// options.west.fxSettings_open.duration
				|| o.fxSpeed				// options.west.fxSpeed
				|| o.fxSettings.duration	// options.west.fxSettings.duration
				|| defs.fxSpeed			// options.defaults.fxSpeed
				|| defs.fxSettings.duration// options.defaults.fxSettings.duration
				|| fx_pane.duration		// effects.slide.west.duration
				|| fx_all.duration			// effects.slide.all.duration
				|| "normal"				// DEFAULT
				;
					// DEBUG: if (pane=="east") debugData( $.extend({}, {speed: o[sSpeed], fxSettings_duration: o[sSettings].duration}, o[sSettings]), pane+"."+sName+" = "+fxName );
				});
			});
		};

		/**
		* initPanes
		*
		* Initialize module objects, styling, size and position for all panes
		*
		* @callers  create()
		*/
		var initPanes = function() {
			// NOTE: do north & south FIRST so we can measure their height - do center LAST
			$.each(c.allPanes.split(","), function() {
				var 
				pane = str(this)
			, o = options[pane]
			, s = state[pane]
			, fx = s.fx
			, dir = c[pane].dir
				//	if o.size is not > 0, then we will use MEASURE the pane and use that as it's 'size'
			, size = o.size == "auto" || isNaN(o.size) ? 0 : o.size
			, minSize = o.minSize || 1
			, maxSize = o.maxSize || 9999
			, spacing = o.spacing_open || 0
			, sel = o.paneSelector
			, isIE6 = ($.browser.msie && $.browser.version < 7)
			, CSS = {}
			, $P, $C
			;
				$Cs[pane] = false; // init

				if (sel.substr(0, 1) === "#") // ID selector
				// NOTE: elements selected 'by ID' DO NOT have to be 'children'
					$P = $Ps[pane] = $Container.find(sel + ":first");
				else { // class or other selector
					$P = $Ps[pane] = $Container.children(sel + ":first");
					// look for the pane nested inside a 'form' element
					if (!$P.length) $P = $Ps[pane] = $Container.children("form:first").children(sel + ":first");
				}

				if (!$P.length) {
					$Ps[pane] = false; // logic
					return true; // SKIP to next
				}

				// add basic classes & attributes
				$P
				.attr("pane", pane) // add pane-identifier
				.addClass(o.paneClass + " " + o.paneClass + "-" + pane) // default = "ui-layout-pane ui-layout-pane-west" - may be a dupe of 'paneSelector'
			;

				// init pane-logic vars, etc.
				if (pane != "center") {
					s.isClosed = false; // true = pane is closed
					s.isSliding = false; // true = pane is currently open by 'sliding' over adjacent panes
					s.isResizing = false; // true = pane is in process of being resized
					s.isHidden = false; // true = pane is hidden - no spacing, resizer or toggler is visible!
					s.noRoom = false; // true = pane 'automatically' hidden due to insufficient room - will unhide automatically
					// create special keys for internal use
					c[pane].pins = [];   // used to track and sync 'pin-buttons' for border-panes
				}

				CSS = $.extend({ visibility: "visible", display: "block" }, c.defaults.cssReq, c[pane].cssReq);
				if (o.applyDefaultStyles) $.extend(CSS, c.defaults.cssDef, c[pane].cssDef); // cosmetic defaults
				$P.css(CSS); // add base-css BEFORE 'measuring' to calc size & position
				CSS = {}; // reset var

				// set css-position to account for container borders & padding
				switch (pane) {
					case "north": CSS.top = cDims.top;
						CSS.left = cDims.left;
						CSS.right = cDims.right;
						break;
					case "south": CSS.bottom = cDims.bottom;
						CSS.left = cDims.left;
						CSS.right = cDims.right;
						break;
					case "west": CSS.left = cDims.left; // top, bottom & height set by sizeMidPanes()
						break;
					case "east": CSS.right = cDims.right; // ditto
						break;
					case "center": // top, left, width & height set by sizeMidPanes()
				}

				if (dir == "horz") { // north or south pane
					if (size === 0 || size == "auto") {
						$P.css({ height: "auto" });
						size = $P.outerHeight();
					}
					size = max(size, minSize);
					size = min(size, maxSize);
					size = min(size, cDims.innerHeight - spacing);
					CSS.height = max(1, cssH(pane, size));
					s.size = size; // update state
					// make sure minSize is sufficient to avoid errors
					s.maxSize = maxSize; // init value
					s.minSize = max(minSize, size - CSS.height + 1); // = pane.outerHeight when css.height = 1px
					// handle IE6
					//if (isIE6) CSS.width = cssW($P, cDims.innerWidth);
					$P.css(CSS); // apply size & position
				}
				else if (dir == "vert") { // east or west pane
					if (size === 0 || size == "auto") {
						$P.css({ width: "auto", float: "left" }); // float = FORCE pane to auto-size
						size = $P.outerWidth();
						$P.css({ float: "none" }); // RESET
					}
					size = max(size, minSize);
					size = min(size, maxSize);
					size = min(size, cDims.innerWidth - spacing);
					CSS.width = max(1, cssW(pane, size));
					s.size = size; // update state
					s.maxSize = maxSize; // init value
					// make sure minSize is sufficient to avoid errors
					s.minSize = max(minSize, size - CSS.width + 1); // = pane.outerWidth when css.width = 1px
					$P.css(CSS); // apply size - top, bottom & height set by sizeMidPanes
					sizeMidPanes(pane, null, true); // true = onInit
				}
				else if (pane == "center") {
					$P.css(CSS); // top, left, width & height set by sizeMidPanes...
					sizeMidPanes("center", null, true); // true = onInit
				}

				// close or hide the pane if specified in settings
				if (o.initClosed && o.closable) {
					$P.hide().addClass("closed");
					s.isClosed = true;
				}
				else if (o.initHidden || o.initClosed) {
					hide(pane, true); // will be completely invisible - no resizer or spacing
					s.isHidden = true;
				}
				else
					$P.addClass("open");

				// check option for auto-handling of pop-ups & drop-downs
				if (o.showOverflowOnHover)
					$P.hover(allowOverflow, resetOverflow);

				/*
				*	see if this pane has a 'content element' that we need to auto-size
				*/
				if (o.contentSelector) {
					$C = $Cs[pane] = $P.children(o.contentSelector + ":first"); // match 1-element only
					if (!$C.length) {
						$Cs[pane] = false;
						return true; // SKIP to next
					}
					$C.css(c.content.cssReq);
					if (o.applyDefaultStyles) $C.css(c.content.cssDef); // cosmetic defaults
					// NO PANE-SCROLLING when there is a content-div
					$P.css({ overflow: "hidden" });
				}
			});
		};

		/**
		* initHandles
		*
		* Initialize module objects, styling, size and position for all resize bars and toggler buttons
		*
		* @callers  create()
		*/
		var initHandles = function() {
			// create toggler DIVs for each pane, and set object pointers for them, eg: $R.north = north toggler DIV
			$.each(c.borderPanes.split(","), function() {
				var 
				pane = str(this)
			, o = options[pane]
			, s = state[pane]
			, rClass = o.resizerClass
			, tClass = o.togglerClass
			, $P = $Ps[pane]
			;
				$Rs[pane] = false; // INIT
				$Ts[pane] = false;

				if (!$P || (!o.closable && !o.resizable)) return; // pane does not exist - skip

				var 
				edge = c[pane].edge
			, isOpen = $P.is(":visible")
			, spacing = (isOpen ? o.spacing_open : o.spacing_closed)
			, _pane = "-" + pane // used for classNames
			, _state = (isOpen ? "-open" : "-closed") // used for classNames
			, $R, $T
			;
				// INIT RESIZER BAR
				$R = $Rs[pane] = $("<span></span>");

				if (isOpen && o.resizable)
					; // this is handled by initResizable
				else if (!isOpen && o.slidable)
					$R.attr("title", o.sliderTip).css("cursor", o.sliderCursor);

				$R
				// if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-resizer"
				.attr("id", (o.paneSelector.substr(0, 1) == "#" ? o.paneSelector.substr(1) + "-resizer" : ""))
				.attr("resizer", pane) // so we can read this from the resizer
				.css(c.resizers.cssReq) // add base/required styles
				// POSITION of resizer bar - allow for container border & padding
				.css(edge, cDims[edge] + getPaneSize(pane))
				// ADD CLASSNAMES - eg: class="resizer resizer-west resizer-open"
				.addClass(rClass + " " + rClass + _pane + " " + rClass + _state + " " + rClass + _pane + _state)
				.appendTo($Container) // append DIV to container
			;
				// ADD VISUAL STYLES
				if (o.applyDefaultStyles)
					$R.css(c.resizers.cssDef);

				if (o.closable) {
					// INIT COLLAPSER BUTTON
					$T = $Ts[pane] = $("<div></div>");
					$T
					// if paneSelector is an ID, then create a matching ID for the resizer, eg: "#paneLeft" => "paneLeft-toggler"
					.attr("id", (o.paneSelector.substr(0, 1) == "#" ? o.paneSelector.substr(1) + "-toggler" : ""))
					.css(c.togglers.cssReq) // add base/required styles
					.attr("title", (isOpen ? o.togglerTip_open : o.togglerTip_closed))
					.click(function(evt) { toggle(pane); evt.stopPropagation(); })
					.mouseover(function(evt) { evt.stopPropagation(); }) // prevent resizer event
					// ADD CLASSNAMES - eg: class="toggler toggler-west toggler-west-open"
					.addClass(tClass + " " + tClass + _pane + " " + tClass + _state + " " + tClass + _pane + _state)
					.appendTo($R) // append SPAN to resizer DIV
				;

					// ADD INNER-SPANS TO TOGGLER
					if (o.togglerContent_open) // ui-layout-open
						$("<span>" + o.togglerContent_open + "</span>")
						.addClass("content content-open")
						.css("display", s.isClosed ? "none" : "block")
						.appendTo($T)
					;
					if (o.togglerContent_closed) // ui-layout-closed
						$("<span>" + o.togglerContent_closed + "</span>")
						.addClass("content content-closed")
						.css("display", s.isClosed ? "block" : "none")
						.appendTo($T)
					;

					// ADD BASIC VISUAL STYLES
					if (o.applyDefaultStyles)
						$T.css(c.togglers.cssDef);

					if (!isOpen) bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true
				}

			});

			// SET ALL HANDLE SIZES & LENGTHS
			sizeHandles("all", true); // true = onInit
		};

		/**
		* initResizable
		*
		* Add resize-bars to all panes that specify it in options
		*
		* @dependancies  $.fn.resizable - will abort if not found
		* @callers  create()
		*/
		var initResizable = function() {
			var 
			draggingAvailable = (typeof $.fn.draggable == "function")
		, minPosition, maxPosition, edge // set in start()
		;

			$.each(c.borderPanes.split(","), function() {
				var 
				pane = str(this)
			, o = options[pane]
			, s = state[pane]
			;
				if (!draggingAvailable || !$Ps[pane] || !o.resizable) {
					o.resizable = false;
					return true; // skip to next
				}

				var 
				rClass = o.resizerClass
				//	'drag' classes are applied to the ORIGINAL resizer-bar while dragging is in process
			, dragClass = rClass + "-drag"			// resizer-drag
			, dragPaneClass = rClass + "-" + pane + "-drag"	// resizer-north-drag
				//	'dragging' class is applied to the CLONED resizer-bar while it is being dragged
			, draggingClass = rClass + "-dragging"		// resizer-dragging
			, draggingPaneClass = rClass + "-" + pane + "-dragging" // resizer-north-dragging
			, draggingClassSet = false 					// logic var
			, $P = $Ps[pane]
			, $R = $Rs[pane]
			;

				if (!s.isClosed)
					$R
					.attr("title", o.resizerTip)
					.css("cursor", o.resizerCursor) // n-resize, s-resize, etc
				;

				$R.draggable({
					containment: $Container[0] // limit resizing to layout container
			, axis: (c[pane].dir == "horz" ? "y" : "x") // limit resizing to horz or vert axis
			, delay: 200
			, distance: 1
					//	basic format for helper - style it using class: .ui-draggable-dragging
			, helper: "clone"
			, opacity: o.resizerDragOpacity
					//,	iframeFix:		o.draggableIframeFix // TODO: consider using when bug is fixed
			, zIndex: c.zIndex.resizing

			, start: function(e, ui) {
				// onresize_start callback - will CANCEL hide if returns false
				// TODO: CONFIRM that dragging can be cancelled like this???
				if (false === execUserCallback(pane, o.onresize_start)) return false;

				s.isResizing = true; // prevent pane from closing while resizing
				clearTimer(pane, "closeSlider"); // just in case already triggered

				$R.addClass(dragClass + " " + dragPaneClass); // add drag classes
				draggingClassSet = false; // reset logic var - see drag()

				// SET RESIZING LIMITS - used in drag()
				var resizerWidth = (pane == "east" || pane == "south" ? o.spacing_open : 0);
				setPaneMinMaxSizes(pane); // update pane-state
				s.minPosition -= resizerWidth;
				s.maxPosition -= resizerWidth;
				edge = (c[pane].dir == "horz" ? "top" : "left");

				// MASK PANES WITH IFRAMES OR OTHER TROUBLESOME ELEMENTS
				$(o.maskIframesOnResize === true ? "iframe" : o.maskIframesOnResize).each(function() {
					$('<div class="ui-layout-mask"/>')
							.css({
								background: "#fff"
							, opacity: "0.001"
							, zIndex: 9
							, position: "absolute"
							, width: this.offsetWidth + "px"
							, height: this.offsetHeight + "px"
							})
							.css($(this).offset()) // top & left
							.appendTo(this.parentNode) // put div INSIDE pane to avoid zIndex issues
						;
				});
			}

			, drag: function(e, ui) {
				if (!draggingClassSet) { // can only add classes after clone has been added to the DOM
					$(".ui-draggable-dragging")
							.addClass(draggingClass + " " + draggingPaneClass) // add dragging classes
							.children().css("visibility", "hidden") // hide toggler inside dragged resizer-bar
						;
					draggingClassSet = true;
					// draggable bug!? RE-SET zIndex to prevent E/W resize-bar showing through N/S pane!
					if (s.isSliding) $Ps[pane].css("zIndex", c.zIndex.sliding);
				}
				// CONTAIN RESIZER-BAR TO RESIZING LIMITS
				if (ui.position[edge] < s.minPosition) ui.position[edge] = s.minPosition;
				else if (ui.position[edge] > s.maxPosition) ui.position[edge] = s.maxPosition;
			}

			, stop: function(e, ui) {
				var 
						dragPos = ui.position
					, resizerPos
					, newSize
					;
				$R.removeClass(dragClass + " " + dragPaneClass); // remove drag classes

				switch (pane) {
					case "north": resizerPos = dragPos.top; break;
					case "west": resizerPos = dragPos.left; break;
					case "south": resizerPos = cDims.outerHeight - dragPos.top - $R.outerHeight(); break;
					case "east": resizerPos = cDims.outerWidth - dragPos.left - $R.outerWidth(); break;
				}
				// remove container margin from resizer position to get the pane size
				newSize = resizerPos - cDims[c[pane].edge];

				sizePane(pane, newSize);

				// UN-MASK PANES MASKED IN drag.start
				$("div.ui-layout-mask").remove(); // Remove iframe masks	

				s.isResizing = false;
			}

				});
			});
		};



		/*
		* ###########################
		*       ACTION METHODS
		* ###########################
		*/

		/**
		* hide / show
		*
		* Completely 'hides' a pane, including its spacing - as if it does not exist
		* The pane is not actually 'removed' from the source, so can use 'show' to un-hide it
		*
		* @param String  pane   The pane being hidden, ie: north, south, east, or west
		*/
		var hide = function(pane, onInit) {
			var 
			o = options[pane]
		, s = state[pane]
		, $P = $Ps[pane]
		, $R = $Rs[pane]
		;
			if (!$P || s.isHidden) return; // pane does not exist OR is already hidden

			// onhide_start callback - will CANCEL hide if returns false
			if (false === execUserCallback(pane, o.onhide_start)) return;

			s.isSliding = false; // just in case

			// now hide the elements
			if ($R) $R.hide(); // hide resizer-bar
			if (onInit || s.isClosed) {
				s.isClosed = true; // to trigger open-animation on show()
				s.isHidden = true;
				$P.hide(); // no animation when loading page
				sizeMidPanes(c[pane].dir == "horz" ? "all" : "center");
				execUserCallback(pane, o.onhide_end || o.onhide);
			}
			else {
				s.isHiding = true; // used by onclose
				close(pane, false); // adjust all panes to fit
				//s.isHidden  = true; - will be set by close - if not cancelled
			}
		};

		var show = function(pane, openPane) {
			var 
			o = options[pane]
		, s = state[pane]
		, $P = $Ps[pane]
		, $R = $Rs[pane]
		;
			if (!$P || !s.isHidden) return; // pane does not exist OR is not hidden

			// onhide_start callback - will CANCEL hide if returns false
			if (false === execUserCallback(pane, o.onshow_start)) return;

			s.isSliding = false; // just in case
			s.isShowing = true; // used by onopen/onclose
			//s.isHidden  = false; - will be set by open/close - if not cancelled

			// now show the elements
			if ($R && o.spacing_open > 0) $R.show();
			if (openPane === false)
				close(pane, true); // true = force
			else
				open(pane); // adjust all panes to fit
		};


		/**
		* toggle
		*
		* Toggles a pane open/closed by calling either open or close
		*
		* @param String  pane   The pane being toggled, ie: north, south, east, or west
		*/
		var toggle = function(pane) {
			var s = state[pane];
			if (s.isHidden)
				show(pane); // will call 'open' after unhiding it
			else if (s.isClosed)
				open(pane);
			else
				close(pane);
		};

		/**
		* close
		*
		* Close the specified pane (animation optional), and resize all other panes as needed
		*
		* @param String  pane   The pane being closed, ie: north, south, east, or west
		*/
		var close = function(pane, force, noAnimation) {
			var 
			$P = $Ps[pane]
		, $R = $Rs[pane]
		, $T = $Ts[pane]
		, o = options[pane]
		, s = state[pane]
		, doFX = !noAnimation && !s.isClosed && (o.fxName_close != "none")
		, edge = c[pane].edge
		, rClass = o.resizerClass
		, tClass = o.togglerClass
		, _pane = "-" + pane // used for classNames
		, _open = "-open"
		, _sliding = "-sliding"
		, _closed = "-closed"
			// 	transfer logic vars to temp vars
		, isShowing = s.isShowing
		, isHiding = s.isHiding
		;
			// now clear the logic vars
			delete s.isShowing;
			delete s.isHiding;

			if (!$P || (!o.resizable && !o.closable)) return; // invalid request
			else if (!force && s.isClosed && !isShowing) return; // already closed

			if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation
				setFlowCallback("close", pane, force); // set a callback for this action, if possible
				return; // ABORT 
			}

			// onclose_start callback - will CANCEL hide if returns false
			// SKIP if just 'showing' a hidden pane as 'closed'
			if (!isShowing && false === execUserCallback(pane, o.onclose_start)) return;

			// SET flow-control flags
			c[pane].isMoving = true;
			c.isLayoutBusy = true;

			s.isClosed = true;
			// update isHidden BEFORE sizing panes
			if (isHiding) s.isHidden = true;
			else if (isShowing) s.isHidden = false;

			// sync any 'pin buttons'
			syncPinBtns(pane, false);

			// resize panes adjacent to this one
			if (!s.isSliding) sizeMidPanes(c[pane].dir == "horz" ? "all" : "center");

			// if this pane has a resizer bar, move it now
			if ($R) {
				$R
				.css(edge, cDims[edge]) // move the resizer bar
				.removeClass(rClass + _open + " " + rClass + _pane + _open)
				.removeClass(rClass + _sliding + " " + rClass + _pane + _sliding)
				.addClass(rClass + _closed + " " + rClass + _pane + _closed)
			;
				// DISABLE 'resizing' when closed - do this BEFORE bindStartSlidingEvent
				if (o.resizable)
					$R
					.draggable("disable")
					.css("cursor", "default")
					.attr("title", "")
				;
				// if pane has a toggler button, adjust that too
				if ($T) {
					$T
					.removeClass(tClass + _open + " " + tClass + _pane + _open)
					.addClass(tClass + _closed + " " + tClass + _pane + _closed)
					.attr("title", o.togglerTip_closed) // may be blank
				;
				}
				sizeHandles(); // resize 'length' and position togglers for adjacent panes
			}

			// ANIMATE 'CLOSE' - if no animation, then was ALREADY shown above
			if (doFX) {
				lockPaneForFX(pane, true); // need to set left/top so animation will work
				$P.hide(o.fxName_close, o.fxSettings_close, o.fxSpeed_close, function() {
					lockPaneForFX(pane, false); // undo
					if (!s.isClosed) return; // pane was opened before animation finished!
					close_2();
				});
			}
			else {
				$P.hide(); // just hide pane NOW
				close_2();
			}

			// SUBROUTINE
			function close_2() {
				bindStartSlidingEvent(pane, true); // will enable if state.PANE.isSliding = true

				// onclose callback - UNLESS just 'showing' a hidden pane as 'closed'
				if (!isShowing) execUserCallback(pane, o.onclose_end || o.onclose);
				// onhide OR onshow callback
				if (isShowing) execUserCallback(pane, o.onshow_end || o.onshow);
				if (isHiding) execUserCallback(pane, o.onhide_end || o.onhide);

				// internal flow-control callback
				execFlowCallback(pane);
			}
		};

		/**
		* open
		*
		* Open the specified pane (animation optional), and resize all other panes as needed
		*
		* @param String  pane   The pane being opened, ie: north, south, east, or west
		*/
		var open = function(pane, slide, noAnimation) {
			var 
			$P = $Ps[pane]
		, $R = $Rs[pane]
		, $T = $Ts[pane]
		, o = options[pane]
		, s = state[pane]
		, doFX = !noAnimation && s.isClosed && (o.fxName_open != "none")
		, edge = c[pane].edge
		, rClass = o.resizerClass
		, tClass = o.togglerClass
		, _pane = "-" + pane // used for classNames
		, _open = "-open"
		, _closed = "-closed"
		, _sliding = "-sliding"
			// 	transfer logic var to temp var
		, isShowing = s.isShowing
		;
			// now clear the logic var
			delete s.isShowing;

			if (!$P || (!o.resizable && !o.closable)) return; // invalid request
			else if (!s.isClosed && !s.isSliding) return; // already open

			// pane can ALSO be unhidden by just calling show(), so handle this scenario
			if (s.isHidden && !isShowing) {
				show(pane, true);
				return;
			}

			if (c.isLayoutBusy) { // layout is 'busy' - probably with an animation
				setFlowCallback("open", pane, slide); // set a callback for this action, if possible
				return; // ABORT
			}

			// onopen_start callback - will CANCEL hide if returns false
			if (false === execUserCallback(pane, o.onopen_start)) return;

			// SET flow-control flags
			c[pane].isMoving = true;
			c.isLayoutBusy = true;

			// 'PIN PANE' - stop sliding
			if (s.isSliding && !slide) // !slide = 'open pane normally' - NOT sliding
				bindStopSlidingEvents(pane, false); // will set isSliding=false

			s.isClosed = false;
			// update isHidden BEFORE sizing panes
			if (isShowing) s.isHidden = false;

			// Container size may have changed - shrink the pane if now 'too big'
			setPaneMinMaxSizes(pane); // update pane-state
			if (s.size > s.maxSize) // pane is too big! resize it before opening
				$P.css(c[pane].sizeType, max(1, cssSize(pane, s.maxSize)));

			bindStartSlidingEvent(pane, false); // remove trigger event from resizer-bar

			if (doFX) { // ANIMATE
				lockPaneForFX(pane, true); // need to set left/top so animation will work
				$P.show(o.fxName_open, o.fxSettings_open, o.fxSpeed_open, function() {
					lockPaneForFX(pane, false); // undo
					if (s.isClosed) return; // pane was closed before animation finished!
					open_2(); // continue
				});
			}
			else {// no animation
				$P.show(); // just show pane and...
				open_2(); // continue
			}

			// SUBROUTINE
			function open_2() {
				// NOTE: if isSliding, then other panes are NOT 'resized'
				if (!s.isSliding) // resize all panes adjacent to this one
					sizeMidPanes(c[pane].dir == "vert" ? "center" : "all");

				// if this pane has a toggler, move it now
				if ($R) {
					$R
					.css(edge, cDims[edge] + getPaneSize(pane)) // move the toggler
					.removeClass(rClass + _closed + " " + rClass + _pane + _closed)
					.addClass(rClass + _open + " " + rClass + _pane + _open)
					.addClass(!s.isSliding ? "" : rClass + _sliding + " " + rClass + _pane + _sliding)
				;
					if (o.resizable)
						$R
						.draggable("enable")
						.css("cursor", o.resizerCursor)
						.attr("title", o.resizerTip)
					;
					else
						$R.css("cursor", "default"); // n-resize, s-resize, etc
					// if pane also has a toggler button, adjust that too
					if ($T) {
						$T
						.removeClass(tClass + _closed + " " + tClass + _pane + _closed)
						.addClass(tClass + _open + " " + tClass + _pane + _open)
						.attr("title", o.togglerTip_open) // may be blank
					;
					}
					sizeHandles("all"); // resize resizer & toggler sizes for all panes
				}

				// resize content every time pane opens - to be sure
				sizeContent(pane);

				// sync any 'pin buttons'
				syncPinBtns(pane, !s.isSliding);

				// onopen callback
				execUserCallback(pane, o.onopen_end || o.onopen);

				// onshow callback
				if (isShowing) execUserCallback(pane, o.onshow_end || o.onshow);

				// internal flow-control callback
				execFlowCallback(pane);
			}
		};


		/**
		* lockPaneForFX
		*
		* Must set left/top on East/South panes so animation will work properly
		*
		* @param String  pane  The pane to lock, 'east' or 'south' - any other is ignored!
		* @param Boolean  doLock  true = set left/top, false = remove
		*/
		var lockPaneForFX = function(pane, doLock) {
			var $P = $Ps[pane];
			if (doLock) {
				$P.css({ zIndex: c.zIndex.animation }); // overlay all elements during animation
				if (pane == "south")
					$P.css({ top: cDims.top + cDims.innerHeight - $P.outerHeight() });
				else if (pane == "east")
					$P.css({ left: cDims.left + cDims.innerWidth - $P.outerWidth() });
			}
			else {
				if (!state[pane].isSliding) $P.css({ zIndex: c.zIndex.pane_normal });
				if (pane == "south")
					$P.css({ top: "auto" });
				else if (pane == "east")
					$P.css({ left: "auto" });
			}
		};


		/**
		* bindStartSlidingEvent
		*
		* Toggle sliding functionality of a specific pane on/off by adding removing 'slide open' trigger
		*
		* @callers  open(), close()
		* @param String  pane  The pane to enable/disable, 'north', 'south', etc.
		* @param Boolean  enable  Enable or Disable sliding?
		*/
		var bindStartSlidingEvent = function(pane, enable) {
			var 
			o = options[pane]
		, $R = $Rs[pane]
		, trigger = o.slideTrigger_open
		;
			if (!$R || !o.slidable) return;
			// make sure we have a valid event
			if (trigger != "click" && trigger != "dblclick" && trigger != "mouseover") trigger = "click";
			$R
			// add or remove trigger event
			[enable ? "bind" : "unbind"](trigger, slideOpen)
			// set the appropriate cursor & title/tip
			.css("cursor", (enable ? o.sliderCursor : "default"))
			.attr("title", (enable ? o.sliderTip : ""))
		;
		};

		/**
		* bindStopSlidingEvents
		*
		* Add or remove 'mouseout' events to 'slide close' when pane is 'sliding' open or closed
		* Also increases zIndex when pane is sliding open
		* See bindStartSlidingEvent for code to control 'slide open'
		*
		* @callers  slideOpen(), slideClosed()
		* @param String  pane  The pane to process, 'north', 'south', etc.
		* @param Boolean  isOpen  Is pane open or closed?
		*/
		var bindStopSlidingEvents = function(pane, enable) {
			var 
			o = options[pane]
		, s = state[pane]
		, trigger = o.slideTrigger_close
		, action = (enable ? "bind" : "unbind") // can't make 'unbind' work! - see disabled code below
		, $P = $Ps[pane]
		, $R = $Rs[pane]
		;

			s.isSliding = enable; // logic
			clearTimer(pane, "closeSlider"); // just in case

			// raise z-index when sliding
			$P.css({ zIndex: (enable ? c.zIndex.sliding : c.zIndex.pane_normal) });
			$R.css({ zIndex: (enable ? c.zIndex.sliding : c.zIndex.resizer_normal) });

			// make sure we have a valid event
			if (trigger != "click" && trigger != "mouseout") trigger = "mouseout";

			// when trigger is 'mouseout', must cancel timer when mouse moves between 'pane' and 'resizer'
			if (enable) { // BIND trigger events
				$P.bind(trigger, slideClosed);
				$R.bind(trigger, slideClosed);
				if (trigger = "mouseout") {
					$P.bind("mouseover", cancelMouseOut);
					$R.bind("mouseover", cancelMouseOut);
				}
			}
			else { // UNBIND trigger events
				// TODO: why does unbind of a 'single function' not work reliably?
				//$P[action](trigger, slideClosed );
				$P.unbind(trigger);
				$R.unbind(trigger);
				if (trigger = "mouseout") {
					//$P[action]("mouseover", cancelMouseOut );
					$P.unbind("mouseover");
					$R.unbind("mouseover");
					clearTimer(pane, "closeSlider");
				}
			}

			// SUBROUTINE for mouseout timer clearing
			function cancelMouseOut(evt) {
				clearTimer(pane, "closeSlider");
				evt.stopPropagation();
			}
		};

		var slideOpen = function() {
			var pane = $(this).attr("resizer"); // attr added by initHandles
			if (state[pane].isClosed) { // skip if already open!
				bindStopSlidingEvents(pane, true); // pane is opening, so BIND trigger events to close it
				open(pane, true); // true = slide - ie, called from here!
			}
		};

		var slideClosed = function() {
			var 
			$E = $(this)
		, pane = $E.attr("pane") || $E.attr("resizer")
		, o = options[pane]
		, s = state[pane]
		;
			if (s.isClosed || s.isResizing)
				return; // skip if already closed OR in process of resizing
			else if (o.slideTrigger_close == "click")
				close_NOW(); // close immediately onClick
			else // trigger = mouseout - use a delay
				setTimer(pane, "closeSlider", close_NOW, 300); // .3 sec delay

			// SUBROUTINE for timed close
			function close_NOW() {
				bindStopSlidingEvents(pane, false); // pane is being closed, so UNBIND trigger events
				if (!s.isClosed) close(pane); // skip if already closed!
			}
		};


		/**
		* sizePane
		*
		* @callers  initResizable.stop()
		* @param String  pane   The pane being resized - usually west or east, but potentially north or south
		* @param Integer  newSize  The new size for this pane - will be validated
		*/
		var sizePane = function(pane, size) {
			// TODO: accept "auto" as size, and size-to-fit pane content
			var 
			edge = c[pane].edge
		, dir = c[pane].dir
		, o = options[pane]
		, s = state[pane]
		, $P = $Ps[pane]
		, $R = $Rs[pane]
		;
			// calculate 'current' min/max sizes
			setPaneMinMaxSizes(pane); // update pane-state
			// compare/update calculated min/max to user-options
			s.minSize = max(s.minSize, o.minSize);
			if (o.maxSize > 0) s.maxSize = min(s.maxSize, o.maxSize);
			// validate passed size
			size = max(size, s.minSize);
			size = min(size, s.maxSize);
			s.size = size; // update state

			// move the resizer bar and resize the pane
			$R.css(edge, size + cDims[edge]);
			$P.css(c[pane].sizeType, max(1, cssSize(pane, size)));

			// resize all the adjacent panes, and adjust their toggler buttons
			if (!s.isSliding) sizeMidPanes(dir == "horz" ? "all" : "center");
			sizeHandles();
			sizeContent(pane);
			execUserCallback(pane, o.onresize_end || o.onresize);
		};

		/**
		* sizeMidPanes
		*
		* @callers  create(), open(), close(), onWindowResize()
		*/
		var sizeMidPanes = function(panes, overrideDims, onInit) {
			if (!panes || panes == "all") panes = "east,west,center";

			var d = getPaneDims();
			if (overrideDims) $.extend(d, overrideDims);

			$.each(panes.split(","), function() {
				if (!$Ps[this]) return; // NO PANE - skip
				var 
				pane = str(this)
			, o = options[pane]
			, s = state[pane]
			, $P = $Ps[pane]
			, $R = $Rs[pane]
			, hasRoom = true
			, CSS = {}
			;

				if (pane == "center") {
					d = getPaneDims(); // REFRESH Dims because may have just 'unhidden' East or West pane after a 'resize'
					CSS = $.extend({}, d); // COPY ALL of the paneDims
					CSS.width = max(1, cssW(pane, CSS.width));
					CSS.height = max(1, cssH(pane, CSS.height));
					hasRoom = (CSS.width > 1 && CSS.height > 1);
					/*
					* Extra CSS for IE6 or IE7 in Quirks-mode - add 'width' to NORTH/SOUTH panes
					* Normally these panes have only 'left' & 'right' positions so pane auto-sizes
					*/
					if ($.browser.msie && (!$.boxModel || $.browser.version < 7)) {
						if ($Ps.north) $Ps.north.css({ width: cssW($Ps.north, cDims.innerWidth) });
						if ($Ps.south) $Ps.south.css({ width: cssW($Ps.south, cDims.innerWidth) });
					}
				}
				else { // for east and west, set only the height
					CSS.top = d.top;
					CSS.bottom = d.bottom;
					CSS.height = max(1, cssH(pane, d.height));
					hasRoom = (CSS.height > 1);
				}

				if (hasRoom) {
					$P.css(CSS);
					if (s.noRoom) {
						s.noRoom = false;
						if (s.isHidden) return;
						else show(pane, !s.isClosed);
						/* OLD CODE - keep until sure line above works right!
						if (!s.isClosed) $P.show(); // in case was previously hidden due to NOT hasRoom
						if ($R) $R.show();
						*/
					}
					if (!onInit) {
						sizeContent(pane);
						execUserCallback(pane, o.onresize_end || o.onresize);
					}
				}
				else if (!s.noRoom) { // no room for pane, so just hide it (if not already)
					s.noRoom = true; // update state
					if (s.isHidden) return;
					if (onInit) { // skip onhide callback and other logic onLoad
						$P.hide();
						if ($R) $R.hide();
					}
					else hide(pane);
				}
			});
		};


		var sizeContent = function(panes) {
			if (!panes || panes == "all") panes = c.allPanes;

			$.each(panes.split(","), function() {
				if (!$Cs[this]) return; // NO CONTENT - skip
				var 
				pane = str(this)
			, ignore = options[pane].contentIgnoreSelector
			, $P = $Ps[pane]
			, $C = $Cs[pane]
			, e_C = $C[0]		// DOM element
			, height = cssH($P); // init to pane.innerHeight
				;
				$P.children().each(function() {
					if (this == e_C) return; // Content elem - skip
					var $E = $(this);
					if (!ignore || !$E.is(ignore))
						height -= $E.outerHeight();
				});
				if (height > 0)
					height = cssH($C, height);
				if (height < 1)
					$C.hide(); // no room for content!
				else
					$C.css({ height: height }).show();
			});
		};


		/**
		* sizeHandles
		*
		* Called every time a pane is opened, closed, or resized to slide the togglers to 'center' and adjust their length if necessary
		*
		* @callers  initHandles(), open(), close(), resizeAll()
		*/
		var sizeHandles = function(panes, onInit) {
			if (!panes || panes == "all") panes = c.borderPanes;

			$.each(panes.split(","), function() {
				var 
				pane = str(this)
			, o = options[pane]
			, s = state[pane]
			, $P = $Ps[pane]
			, $R = $Rs[pane]
			, $T = $Ts[pane]
			;
				if (!$P || !$R || (!o.resizable && !o.closable)) return; // skip

				var 
				dir = c[pane].dir
			, _state = (s.isClosed ? "_closed" : "_open")
			, spacing = o["spacing" + _state]
			, togAlign = o["togglerAlign" + _state]
			, togLen = o["togglerLength" + _state]
			, paneLen
			, offset
			, CSS = {}
			;
				if (spacing == 0) {
					$R.hide();
					return;
				}
				else if (!s.noRoom && !s.isHidden) // skip if resizer was hidden for any reason
					$R.show(); // in case was previously hidden

				// Resizer Bar is ALWAYS same width/height of pane it is attached to
				if (dir == "horz") { // north/south
					paneLen = $P.outerWidth();
					$R.css({
						width: max(1, cssW($R, paneLen)) // account for borders & padding
				, height: max(1, cssH($R, spacing)) // ditto
				, left: cssNum($P, "left")
					});
				}
				else { // east/west
					paneLen = $P.outerHeight();
					$R.css({
						height: max(1, cssH($R, paneLen)) // account for borders & padding
				, width: max(1, cssW($R, spacing)) // ditto
				, top: cDims.top + getPaneSize("north", true)
						//,	top:	cssNum($Ps["center"], "top")
					});

				}

				if ($T) {
					if (togLen == 0 || (s.isSliding && o.hideTogglerOnSlide)) {
						$T.hide(); // always HIDE the toggler when 'sliding'
						return;
					}
					else
						$T.show(); // in case was previously hidden

					if (!(togLen > 0) || togLen == "100%" || togLen > paneLen) {
						togLen = paneLen;
						offset = 0;
					}
					else { // calculate 'offset' based on options.PANE.togglerAlign_open/closed
						if (typeof togAlign == "string") {
							switch (togAlign) {
								case "top":
								case "left": offset = 0;
									break;
								case "bottom":
								case "right": offset = paneLen - togLen;
									break;
								case "middle":
								case "center":
								default: offset = Math.floor((paneLen - togLen) / 2); // 'default' catches typos
							}
						}
						else { // togAlign = number
							var x = parseInt(togAlign); //
							if (togAlign >= 0) offset = x;
							else offset = paneLen - togLen + x; // NOTE: x is negative!
						}
					}

					var 
					$TC_o = (o.togglerContent_open ? $T.children(".content-open") : false)
				, $TC_c = (o.togglerContent_closed ? $T.children(".content-closed") : false)
				, $TC = (s.isClosed ? $TC_c : $TC_o)
				;
					if ($TC_o) $TC_o.css("display", s.isClosed ? "none" : "block");
					if ($TC_c) $TC_c.css("display", s.isClosed ? "block" : "none");

					if (dir == "horz") { // north/south
						var width = cssW($T, togLen);
						$T.css({
							width: max(0, width)  // account for borders & padding
					, height: max(1, cssH($T, spacing)) // ditto
					, left: offset // TODO: VERIFY that toggler  positions correctly for ALL values
						});
						if ($TC) // CENTER the toggler content SPAN
							$TC.css("marginLeft", Math.floor((width - $TC.outerWidth()) / 2)); // could be negative
					}
					else { // east/west
						var height = cssH($T, togLen);
						$T.css({
							height: max(0, height)  // account for borders & padding
					, width: max(1, cssW($T, spacing)) // ditto
					, top: offset // POSITION the toggler
						});
						if ($TC) // CENTER the toggler content SPAN
							$TC.css("marginTop", Math.floor((height - $TC.outerHeight()) / 2)); // could be negative
					}


				}

				// DONE measuring and sizing this resizer/toggler, so can be 'hidden' now
				if (onInit && o.initHidden) {
					$R.hide();
					if ($T) $T.hide();
				}
			});
		};


		/**
		* resizeAll
		*
		* @callers  window.onresize(), callbacks or custom code
		*/
		var resizeAll = function() {
			var 
			oldW = cDims.innerWidth
		, oldH = cDims.innerHeight
		;
			cDims = state.container = getElemDims($Container); // UPDATE container dimensions

			var 
			checkH = (cDims.innerHeight < oldH)
		, checkW = (cDims.innerWidth < oldW)
		, s, dir
		;

			if (checkH || checkW)
			// NOTE special order for sizing: S-N-E-W
				$.each(["south", "north", "east", "west"], function(i, pane) {
					s = state[pane];
					dir = c[pane].dir;
					if (!s.isClosed && ((checkH && dir == "horz") || (checkW && dir == "vert"))) {
						setPaneMinMaxSizes(pane); // update pane-state
						// shrink pane if 'too big' to fit
						if (s.size > s.maxSize)
							sizePane(pane, s.maxSize);
					}
				});

			sizeMidPanes("all");
			sizeHandles("all"); // reposition the toggler elements
		};


		/**
		* keyDown
		*
		* Capture keys when enableCursorHotkey - toggle pane if hotkey pressed
		*
		* @callers  document.keydown()
		*/
		function keyDown(evt) {
			if (!evt) return true;
			var code = evt.keyCode;
			if (code < 33) return true; // ignore special keys: ENTER, TAB, etc

			var 
			PANE = {
				38: "north" // Up Cursor
			, 40: "south" // Down Cursor
			, 37: "west"  // Left Cursor
			, 39: "east"  // Right Cursor
			}
		, isCursorKey = (code >= 37 && code <= 40)
		, ALT = evt.altKey // no worky!
		, SHIFT = evt.shiftKey
		, CTRL = evt.ctrlKey
		, pane = false
		, s, o, k, m, el
		;

			if (!CTRL && !SHIFT)
				return true; // no modifier key - abort
			else if (isCursorKey && options[PANE[code]].enableCursorHotkey) // valid cursor-hotkey
				pane = PANE[code];
			else // check to see if this matches a custom-hotkey
				$.each(c.borderPanes.split(","), function(i, p) { // loop each pane to check its hotkey
					o = options[p];
					k = o.customHotkey;
					m = o.customHotkeyModifier; // if missing or invalid, treated as "CTRL+SHIFT"
					if ((SHIFT && m == "SHIFT") || (CTRL && m == "CTRL") || (CTRL && SHIFT)) { // Modifier matches
						if (k && code == (isNaN(k) || k <= 9 ? k.toUpperCase().charCodeAt(0) : k)) { // Key matches
							pane = p;
							return false; // BREAK
						}
					}
				});

			if (!pane) return true; // no hotkey - abort

			// validate pane
			o = options[pane]; // get pane options
			s = state[pane]; // get pane options
			if (!o.enableCursorHotkey || s.isHidden || !$Ps[pane]) return true;

			// see if user is in a 'form field' because may be 'selecting text'!
			el = evt.target || evt.srcElement;
			if (el && SHIFT && isCursorKey && (el.tagName == "TEXTAREA" || (el.tagName == "INPUT" && (code == 37 || code == 39))))
				return true; // allow text-selection

			// SYNTAX NOTES
			// use "returnValue=false" to abort keystroke but NOT abort function - can run another command afterwards
			// use "return false" to abort keystroke AND abort function
			toggle(pane);
			evt.stopPropagation();
			evt.returnValue = false; // CANCEL key
			return false;
		};


		/*
		* ###########################
		*     UTILITY METHODS
		*   called externally only
		* ###########################
		*/

		function allowOverflow(elem) {
			if (this && this.tagName) elem = this; // BOUND to element
			var $P;
			if (typeof elem == "string")
				$P = $Ps[elem];
			else {
				if ($(elem).attr("pane")) $P = $(elem);
				else $P = $(elem).parents("div[pane]:first");
			}
			if (!$P.length) return; // INVALID

			var 
			pane = $P.attr("pane")
		, s = state[pane]
		;

			// if pane is already raised, then reset it before doing it again!
			// this would happen if allowOverflow is attached to BOTH the pane and an element 
			if (s.cssSaved)
				resetOverflow(pane); // reset previous CSS before continuing

			// if pane is raised by sliding or resizing, or it's closed, then abort
			if (s.isSliding || s.isResizing || s.isClosed) {
				s.cssSaved = false;
				return;
			}

			var 
			newCSS = { zIndex: (c.zIndex.pane_normal + 1) }
		, curCSS = {}
		, of = $P.css("overflow")
		, ofX = $P.css("overflowX")
		, ofY = $P.css("overflowY")
		;
			// determine which, if any, overflow settings need to be changed
			if (of != "visible") {
				curCSS.overflow = of;
				newCSS.overflow = "visible";
			}
			if (ofX && ofX != "visible" && ofX != "auto") {
				curCSS.overflowX = ofX;
				newCSS.overflowX = "visible";
			}
			if (ofY && ofY != "visible" && ofY != "auto") {
				curCSS.overflowY = ofX;
				newCSS.overflowY = "visible";
			}

			// save the current overflow settings - even if blank!
			s.cssSaved = curCSS;

			// apply new CSS to raise zIndex and, if necessary, make overflow 'visible'
			$P.css(newCSS);

			// make sure the zIndex of all other panes is normal
			$.each(c.allPanes.split(","), function(i, p) {
				if (p != pane) resetOverflow(p);
			});

		};

		function resetOverflow(elem) {
			if (this && this.tagName) elem = this; // BOUND to element
			var $P;
			if (typeof elem == "string")
				$P = $Ps[elem];
			else {
				if ($(elem).hasClass("ui-layout-pane")) $P = $(elem);
				else $P = $(elem).parents("div[pane]:first");
			}
			if (!$P.length) return; // INVALID

			var 
			pane = $P.attr("pane")
		, s = state[pane]
		, CSS = s.cssSaved || {}
		;
			// reset the zIndex
			if (!s.isSliding && !s.isResizing)
				$P.css("zIndex", c.zIndex.pane_normal);

			// reset Overflow - if necessary
			$P.css(CSS);

			// clear var
			s.cssSaved = false;
		};


		/**
		* getBtn
		*
		* Helper function to validate params received by addButton utilities
		*
		* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .toggle-button"
		* @param String   pane 		Name of the pane the button is for: 'north', 'south', etc.
		* @returns  If both params valid, the element matching 'selector' in a jQuery wrapper - otherwise 'false'
		*/
		function getBtn(selector, pane, action) {
			var 
			$E = $(selector)
		, err = "Error Adding Button \n\nInvalid "
		;
			if (!$E.length) // element not found
				alert(err + "selector: " + selector);
			else if (c.borderPanes.indexOf(pane) == -1) // invalid 'pane' sepecified
				alert(err + "pane: " + pane);
			else { // VALID
				var btn = options[pane].buttonClass + "-" + action;
				$E.addClass(btn + " " + btn + "-" + pane);
				return $E;
			}
			return false;  // INVALID
		};


		/**
		* addToggleBtn
		*
		* Add a custom Toggler button for a pane
		*
		* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .toggle-button"
		* @param String   pane 		Name of the pane the button is for: 'north', 'south', etc.
		*/
		function addToggleBtn(selector, pane) {
			var $E = getBtn(selector, pane, "toggle");
			if ($E)
				$E
				.attr("title", state[pane].isClosed ? "Open" : "Close")
				.click(function(evt) {
					toggle(pane);
					evt.stopPropagation();
				})
			;
		};

		/**
		* addOpenBtn
		*
		* Add a custom Open button for a pane
		*
		* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .open-button"
		* @param String   pane 		Name of the pane the button is for: 'north', 'south', etc.
		*/
		function addOpenBtn(selector, pane) {
			var $E = getBtn(selector, pane, "open");
			if ($E)
				$E
				.attr("title", "Open")
				.click(function(evt) {
					open(pane);
					evt.stopPropagation();
				})
			;
		};

		/**
		* addCloseBtn
		*
		* Add a custom Close button for a pane
		*
		* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .close-button"
		* @param String   pane 		Name of the pane the button is for: 'north', 'south', etc.
		*/
		function addCloseBtn(selector, pane) {
			var $E = getBtn(selector, pane, "close");
			if ($E)
				$E
				.attr("title", "Close")
				.click(function(evt) {
					close(pane);
					evt.stopPropagation();
				})
			;
		};

		/**
		* addPinBtn
		*
		* Add a custom Pin button for a pane
		*
		* Four classes are added to the element, based on the paneClass for the associated pane...
		* Assuming the default paneClass and the pin is 'up', these classes are added for a west-pane pin:
		*  - ui-layout-pane-pin
		*  - ui-layout-pane-west-pin
		*  - ui-layout-pane-pin-up
		*  - ui-layout-pane-west-pin-up
		*
		* @param String   selector 	jQuery selector for button, eg: ".ui-layout-north .ui-layout-pin"
		* @param String   pane 		Name of the pane the pin is for: 'north', 'south', etc.
		*/
		function addPinBtn(selector, pane) {
			var $E = getBtn(selector, pane, "pin");
			if ($E) {
				var s = state[pane];
				$E.click(function(evt) {
					setPinState($(this), pane, (s.isSliding || s.isClosed));
					if (s.isSliding || s.isClosed) open(pane); // change from sliding to open
					else close(pane); // slide-closed
					evt.stopPropagation();
				});
				// add up/down pin attributes and classes
				setPinState($E, pane, (!s.isClosed && !s.isSliding));
				// add this pin to the pane data so we can 'sync it' automatically
				// PANE.pins key is an array so we can store multiple pins for each pane
				c[pane].pins.push(selector); // just save the selector string
			}
		};

		/**
		* syncPinBtns
		*
		* INTERNAL function to sync 'pin buttons' when pane is opened or closed
		* Unpinned means the pane is 'sliding' - ie, over-top of the adjacent panes
		*
		* @callers  open(), close()
		* @params  pane   These are the params returned to callbacks by layout()
		* @params  doPin  True means set the pin 'down', False means 'up'
		*/
		function syncPinBtns(pane, doPin) {
			$.each(c[pane].pins, function(i, selector) {
				setPinState($(selector), pane, doPin);
			});
		};

		/**
		* setPinState
		*
		* Change the class of the pin button to make it look 'up' or 'down'
		*
		* @callers  addPinBtn(), syncPinBtns()
		* @param Element  $Pin		The pin-span element in a jQuery wrapper
		* @param Boolean  doPin		True = set the pin 'down', False = set it 'up'
		* @param String   pinClass	The root classname for pins - will add '-up' or '-down' suffix
		*/
		function setPinState($Pin, pane, doPin) {
			var updown = $Pin.attr("pin");
			if (updown && doPin == (updown == "down")) return; // already in correct state
			var 
			root = options[pane].buttonClass
		, class1 = root + "-pin"
		, class2 = class1 + "-" + pane
		, UP1 = class1 + "-up"
		, UP2 = class2 + "-up"
		, DN1 = class1 + "-down"
		, DN2 = class2 + "-down"
		;
			$Pin
			.attr("pin", doPin ? "down" : "up") // logic
			.attr("title", doPin ? "Un-Pin" : "Pin")
			.removeClass(doPin ? UP1 : DN1)
			.removeClass(doPin ? UP2 : DN2)
			.addClass(doPin ? DN1 : UP1)
			.addClass(doPin ? DN2 : UP2)
		;
		};


		/*
		* ###########################
		* CREATE/RETURN BORDER-LAYOUT
		* ###########################
		*/

		// init global vars
		var 
		$Container = $(this).css({ overflow: "hidden" }) // Container elem
	, $Ps = {} // Panes x4	- set in initPanes()
	, $Cs = {} // Content x4	- set in initPanes()
	, $Rs = {} // Resizers x4	- set in initHandles()
	, $Ts = {} // Togglers x4	- set in initHandles()
		//	object aliases
	, c = config // alias for config hash
	, cDims = state.container // alias for easy access to 'container dimensions'
	;

		// create the border layout NOW
		create();

		// return object pointers to expose data & option Properties, and primary action Methods
		return {
			options: options			// property - options hash
	, state: state			// property - dimensions hash
	, panes: $Ps				// property - object pointers for ALL panes: panes.north, panes.center
	, toggle: toggle			// method - pass a 'pane' ("north", "west", etc)
	, open: open			// method - ditto
	, close: close			// method - ditto
	, hide: hide			// method - ditto
	, show: show			// method - ditto
	, resizeContent: sizeContent		// method - ditto
	, sizePane: sizePane		// method - pass a 'pane' AND a 'size' in pixels
	, resizeAll: resizeAll		// method - no parameters
	, addToggleBtn: addToggleBtn	// utility - pass element selector and 'pane'
	, addOpenBtn: addOpenBtn		// utility - ditto
	, addCloseBtn: addCloseBtn		// utility - ditto
	, addPinBtn: addPinBtn		// utility - ditto
	, allowOverflow: allowOverflow	// utility - pass calling element
	, resetOverflow: resetOverflow	// utility - ditto
	, cssWidth: cssW
	, cssHeight: cssH
		};

	}
})(jQuery);